use vhdl_lang::data;
use vhdl_lang::ast::*;
use super::units::{architecture,entity};
use super::spinal::{Architecture,Entity,Database,Unit,ProcessSpinal};
use vhdl_lang::Project;
use std::path::Path;
use super::peephole;

pub fn get_pos_str(spos:&data::SrcPos) -> String {
    let mut result = String::new();
    let contents = spos.source.contents();
    match contents.get_line(spos.start().line as usize) {
        Some(s) => {
            let mut pos = data::Position {
                line: spos.start().line,
                character: 0,
            };
            for chr in s.chars() {
                if pos < spos.range().start {
                } else if pos < spos.range().end {
                    result.push(chr);
                } else {
                    break;
                }
                pos.character += chr.len_utf16() as u32;
            }
        },
        None => (),
    }
    result
}
pub fn to_str<T>(con:&data::WithPos<T>) -> String {
    get_pos_str(&con.pos)
}

pub fn interface2spinal(port:&InterfaceObjectDeclaration) -> (String,String) {
    let mut result = String::new();
    let mode = match port.mode {
        Mode::In => "in ",
        Mode::Out => "out",
        Mode::InOut => "inout",
        _ => "",
    };
    result += &format!("{} {}",mode,subtype2spinal(&port.subtype_indication));
    if let Some(e) = port.expression.clone() {
        result += &to_str(&e);
    }
    (format!("{}",port.ident.name().name()),result)
}

pub fn ports_of_entity(en:&EntityDeclaration) -> Vec<(String,String)> {
    let mut result = Vec::new();
    for ports in en.port_clause.as_ref().unwrap() {
        match ports {
            InterfaceDeclaration::Type(signal) => {
                result.push((String::new(),format!("Type:{:?}\n",signal)));
            },
            InterfaceDeclaration::Object(signal) => {
                let out = interface2spinal(&signal); 
                result.push(out);
            },
            _ => (),
        }
    }
    result
}

pub fn val(values:Vec<(String,String)>,times:usize) -> String {
    let v = vals(values,times);
    let mut result = String::new();
    for line in v {
        result += &line;
        result += &"\n";
    }
    result
}

pub fn vals(values:Vec<(String,String)>,times:usize) -> Vec<String> {
    let maxlen = values.iter().map(|(id,_)| id.len()).max().unwrap_or(10);
    let mut v = Vec::new();
    let mut values_sort = values.clone();
    values_sort.sort_by_key(|(a,b)| a.clone()); 
    for (id,sub) in values_sort {
        let mut result = String::new();
        result += &(" ".repeat(times));
        result += "val ";
        result += &id;
        result += &(" ".repeat(maxlen-id.len()));
        result += " = ";
        result += &sub;
        v.push(result);
    }
    v
}

pub fn io_bundle(en:&EntityDeclaration) -> String {
    let ports  = ports_of_entity(en);
    let mut result = String::from("val io = new Bundle {\n");
    result += &val(ports,2);
    result += "}\n";
    result
}
pub fn association_element(ae:&AssociationElement) -> String {
    let actual = to_str(&ae.actual);
    if let Some(name) = ae.formal.clone() {
        format!("{}={}",to_str(&name),actual)
    } else {
        format!("{}",actual)
    }
}

pub fn signal2spinal(s:&String) -> String {
    match s.to_uppercase().as_str() {
        "STD_LOGIC" => String::from("Bool"),
        "STD_LOGIC_VECTOR" => String::from("Bits"),
        _ => String::new(),
    }
}

pub fn subtype2spinal(sub:&SubtypeIndication) -> String {
    let mut result = signal2spinal(&to_str(&sub.type_mark));
    if let Some(e) = sub.constraint.clone() {
        result += &constraint2spinal(e);
    }
    result
}

fn constraint2spinal(con:data::WithPos<SubtypeConstraint>) -> String {
    let mut result = String::new();
    match con.item {
        SubtypeConstraint::Range(Range::Range(r)) => {
            result += &range2str(&r);
        },
        SubtypeConstraint::Array(v, ..) => {
            for item in v {
                match item {
                    DiscreteRange::Range(Range::Range(r)) => {
                        result += &range2str(&r);
                    },
                    _ => (),            
                }
            }
        }, 
        _ => (),
    }    
    result    
}

pub fn disrange2str(dr:&DiscreteRange) -> String {
    if let DiscreteRange::Range(Range::Range(r)) = dr {
        range2str(r)
    } else {
        String::new()
    }
}
pub fn range2str(r:&RangeConstraint) -> String {
    let left = to_str(&r.left_expr);
    let right = to_str(&r.right_expr);
    let ret =
    match r.direction {
        Direction::Descending =>  
            format!("(({})-({})+1 bits)",left , right),
        Direction::Ascending => 
            format!("(({})-({})+1 bits)",right , left), 
    };
    peephole::Rules{}.done(ret)
}

pub fn all_instance(project:&Project,base:&Database,file:&String,deep:usize,result :&mut Vec<String>) {
    if let Some(design) = project.get_design(Path::new(&file)) {
        let arches = architecture(&design);
        for arch in arches {
            let ar = Architecture::from(&arch,&base);
            for instance in ar.instances() {
                let selfins = vec![instance.declare()];
                result.push(val(selfins,deep));
                match instance.unit.clone() {
                    Some(Unit::Entity(u)) => { all_instance(project,base,&u.file,deep+4,result);}, 
                    _ => (),
                }
            }
        } 
    }
}

pub fn case_class(project:&Project,base:&Database,file:&String) -> String {
    let mut result = String::new();
    if let Some(design) = project.get_design(Path::new(&file)) {
        let arches = architecture(&design);
        for e in entity(&design) {
            let en = Entity::from(&e);
            for a in &arches {
                if a.entity_name.item.item == e.ident.item {
                    let ar = Architecture::from(&a,&base);
                    let mut ins    = ar.instances_strings();
                    let mut ins_io = ar.instances_io_strings();
                    ins.append(&mut ins_io);
                    result += &en.dumb_case_class(ins);
                }
            }
        } 
    }
    result
}

pub fn assignment_string(a:&ConcurrentSignalAssignment) -> String {
    let mut result = String::from(to_str(&a.target));
    match &a.rhs {
        AssignmentRightHand::Simple(Waveform::Elements(v)) => {
            result += "  =  ";
            result += &to_str(&v[0].value);
        },
        _ => (),
    }
    result
}

pub fn process_string(p:&ProcessStatement) -> String {
    let process = ProcessSpinal::from(p);
    format!("{}",process)
}
pub fn dump_labeled_concurrent_statement(lcs:&LabeledConcurrentStatement) {
    if let Some(label) = &lcs.label {
        println!("statement : {}",label.item);
    } else {
        println!("statement : noname");
    }
    
    match &lcs.statement {
        ConcurrentStatement::ProcedureCall(ProcedureCall) => println!("ProcedureCall"),
        ConcurrentStatement::Block(_b)  => println!("Block"),
        ConcurrentStatement::Process(process)  => println!("Process : \n{}",process_string(process)),
        ConcurrentStatement::Assert(_assert)  => println!("Assert"),
        ConcurrentStatement::Assignment(assign)  => println!("Assignment : {}",assignment_string(assign)),
        ConcurrentStatement::Instance(_i)  => println!("Instance"),
        ConcurrentStatement::ForGenerate(_for)  => println!("ForGenerate"),
        ConcurrentStatement::IfGenerate(_if)  => println!("IfGenerate"),
        ConcurrentStatement::CaseGenerate(_case)  => println!("CaseGenerate"),
    }
}

pub fn dump_architecture(project:&Project,base:&Database,file:&String) {
    let mut result = String::new();
    if let Some(design) = project.get_design(Path::new(&file)) {
        for a in architecture(&design) {
            println!("Id : {}",a.ident);
            println!("En : {}",a.entity_name);
            a.context_clause.iter().for_each(|x| println!("context : {:?}",x.item));
            a.decl.iter().for_each(|x| println!("decl : {:?}",x));
            a.statements.iter().for_each(|x| dump_labeled_concurrent_statement(x));
        }
    }
}